package hu.uszeged.inf.wlab.stunner.provider;

import hu.uszeged.inf.wlab.stunner.database.STUNnerContract;
import hu.uszeged.inf.wlab.stunner.database.STUNnerSqliteHelper;
import hu.uszeged.inf.wlab.stunner.database.utils.SelectionBuilder;

import java.util.ArrayList;

import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;

/**
 * @author szelezsant
 */
public class STUNnerProvider extends ContentProvider {

	/* constants */
	/** The path to the static database. */
	public static final String STUNNER_PATH = STUNnerSqliteHelper.DATABASE_NAME + "/";

	/** The unique code associated with operations handling a single discovery record. */
	private static final int ROUTE_DISCOVERY = 0;
	/** The unique code associated with operations handling multiple discovery records. */
	private static final int ROUTE_DISCOVERIES = 1;
	/** The unique code associated with operations handling a single battery info record. */
	private static final int ROUTE_BATTERY_INFO = 2;
	/** The unique code associated with operations handling a multiple battery info records. */
	private static final int ROUTE_BATTERY_INFOS = 3;
	/** The unique code associated with operations handling a single wifi info record. */
	private static final int ROUTE_WIFI_INFO = 4;
	/** The unique code associated with operations handling a multiple wifi info records. */
	private static final int ROUTE_WIFI_INFOS = 5;
	/** The unique code associated with operations handling a single mobile info record. */
	private static final int ROUTE_MOBILE_INFO = 6;
	/** The unique code associated with operations handling a multiple mobile info records. */
	private static final int ROUTE_MOBILE_INFOS = 7;

	/** String for the unknown URI. */
	private static final String UNKNOWN_URI = "Unknown Uri: ";

	/**
	 * {@link UriMatcher} to identify content.
	 */
	private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);

	/* initialize the uri matcher */
	static {
		URI_MATCHER.addURI(STUNnerContract.AUTHORITY, STUNNER_PATH + STUNnerContract.Discovery.TABLE_NAME + "/#", ROUTE_DISCOVERY);
		URI_MATCHER.addURI(STUNnerContract.AUTHORITY, STUNNER_PATH + STUNnerContract.Discovery.TABLE_NAME, ROUTE_DISCOVERIES);
		URI_MATCHER.addURI(STUNnerContract.AUTHORITY, STUNNER_PATH + STUNnerContract.BatteryInfo.TABLE_NAME + "/#", ROUTE_BATTERY_INFO);
		URI_MATCHER.addURI(STUNnerContract.AUTHORITY, STUNNER_PATH + STUNnerContract.BatteryInfo.TABLE_NAME, ROUTE_BATTERY_INFOS);
		URI_MATCHER.addURI(STUNnerContract.AUTHORITY, STUNNER_PATH + STUNnerContract.WifiInfo.TABLE_NAME, ROUTE_WIFI_INFOS);
		URI_MATCHER.addURI(STUNnerContract.AUTHORITY, STUNNER_PATH + STUNnerContract.WifiInfo.TABLE_NAME + "/#", ROUTE_WIFI_INFO);
		URI_MATCHER.addURI(STUNnerContract.AUTHORITY, STUNNER_PATH + STUNnerContract.MobileNetInfo.TABLE_NAME, ROUTE_MOBILE_INFOS);
		URI_MATCHER.addURI(STUNnerContract.AUTHORITY, STUNNER_PATH + STUNnerContract.MobileNetInfo.TABLE_NAME + "/#", ROUTE_MOBILE_INFO);
	}

	/** The singleton instance of the database helper. */
	private STUNnerSqliteHelper sqliteHelper;

	/* methods */
	/**
	 * Gets the {@link UriMatcher} instance.
	 * 
	 * @return the {@link UriMatcher}
	 */
	public static UriMatcher getUriMatcher() {
		return URI_MATCHER;
	}

	@Override
	public boolean onCreate() {
		if (null == sqliteHelper) {
			sqliteHelper = new STUNnerSqliteHelper(getContext());
		}
		return false;
	}

	/**
	 * Determine the MIME type for entries returned by a given {@link Uri}.
	 * 
	 * @param uri - the actual {@link Uri} to matched
	 * @return the fully qualified Uri
	 */
	@Override
	public String getType(final Uri uri) {
		String result = null;
		switch (URI_MATCHER.match(uri)) {
			case ROUTE_DISCOVERY:
				result = STUNnerContract.Discovery.CONTENT_TYPE_ITEM;
				break;
			case ROUTE_DISCOVERIES:
				result = STUNnerContract.Discovery.CONTENT_TYPE_DIR;
				break;
			case ROUTE_BATTERY_INFO:
				result = STUNnerContract.BatteryInfo.CONTENT_TYPE_ITEM;
				break;
			case ROUTE_BATTERY_INFOS:
				result = STUNnerContract.BatteryInfo.CONTENT_TYPE_DIR;
				break;
			case ROUTE_WIFI_INFO:
				result = STUNnerContract.WifiInfo.CONTENT_TYPE_ITEM;
				break;
			case ROUTE_WIFI_INFOS:
				result = STUNnerContract.WifiInfo.CONTENT_TYPE_DIR;
				break;
			case ROUTE_MOBILE_INFO:
				result = STUNnerContract.MobileNetInfo.CONTENT_TYPE_ITEM;
				break;
			case ROUTE_MOBILE_INFOS:
				result = STUNnerContract.MobileNetInfo.CONTENT_TYPE_DIR;
				break;
			default:
				throw new UnsupportedOperationException(UNKNOWN_URI + uri);
		}
		return result;
	}

	@Override
	public int delete(final Uri uri, final String selection, final String[] selectionArgs) {
		final SelectionBuilder builder = new SelectionBuilder();

		switch (getUriMatcher().match(uri)) {
			case ROUTE_DISCOVERY:
				builder.where(STUNnerContract.Discovery._ID + "=?", uri.getLastPathSegment());
			case ROUTE_DISCOVERIES:
				builder.setTable(STUNnerContract.Discovery.TABLE_NAME).where(selection, selectionArgs);
				break;
			case ROUTE_BATTERY_INFO:
				builder.where(STUNnerContract.BatteryInfo._ID + "=?", uri.getLastPathSegment());
			case ROUTE_BATTERY_INFOS:
				builder.setTable(STUNnerContract.BatteryInfo.TABLE_NAME).where(selection, selectionArgs);
				break;
			case ROUTE_WIFI_INFO:
				builder.where(STUNnerContract.WifiInfo._ID + "=?", uri.getLastPathSegment());
			case ROUTE_WIFI_INFOS:
				builder.setTable(STUNnerContract.WifiInfo.TABLE_NAME).where(selection, selectionArgs);
				break;
			case ROUTE_MOBILE_INFO:
				builder.where(STUNnerContract.MobileNetInfo._ID + "=?", uri.getLastPathSegment());
			case ROUTE_MOBILE_INFOS:
				builder.setTable(STUNnerContract.MobileNetInfo.TABLE_NAME).where(selection, selectionArgs);
				break;
			default:
				throw new UnsupportedOperationException(UNKNOWN_URI + uri);
		}

		final int count = builder.delete(sqliteHelper.getWritableDatabase());
		Log.i(this.getClass().getSimpleName(), "delete: " + count + " records was removed during the operation.");

		final Context context = getContext();
		assert null != context;
		context.getContentResolver().notifyChange(uri, null, false);
		return count;
	}

	@Override
	public Uri insert(final Uri uri, final ContentValues values) {
		final SQLiteDatabase database = sqliteHelper.getWritableDatabase();

		Uri result = null;
		if (ROUTE_DISCOVERIES == getUriMatcher().match(uri)) {
			result = Uri.parse(STUNnerContract.Discovery.CONTENT_URI + "/"
					+ database.insertWithOnConflict(STUNnerContract.Discovery.TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE));

			Log.i(this.getClass().getSimpleName(), "insert: successfully inserted new discovery record: " + result.toString());
		}

		if (ROUTE_BATTERY_INFOS == getUriMatcher().match(uri)) {
			result = Uri.parse(STUNnerContract.BatteryInfo.CONTENT_URI + "/"
					+ database.insertWithOnConflict(STUNnerContract.BatteryInfo.TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE));

			Log.i(this.getClass().getSimpleName(), "insert: successfully inserted new batteryInfo record: " + result.toString());
		}

		if (ROUTE_MOBILE_INFOS == getUriMatcher().match(uri)) {
			result = Uri.parse(STUNnerContract.MobileNetInfo.CONTENT_URI
					+ "/"
					+ database
							.insertWithOnConflict(STUNnerContract.MobileNetInfo.TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE));

			Log.i(this.getClass().getSimpleName(), "insert: successfully inserted new mobile net info record: " + result.toString());
		}

		if (ROUTE_WIFI_INFOS == getUriMatcher().match(uri)) {
			result = Uri.parse(STUNnerContract.WifiInfo.CONTENT_URI + "/"
					+ database.insertWithOnConflict(STUNnerContract.WifiInfo.TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE));

			Log.i(this.getClass().getSimpleName(), "insert: successfully inserted new wifi info record: " + result.toString());
		}

		if (null != result) {
			final Context context = getContext();
			assert context != null;
			context.getContentResolver().notifyChange(uri, null, false);
			return result;
		}

		throw new UnsupportedOperationException(UNKNOWN_URI + uri);

	}

	@Override
	public Cursor query(final Uri uri, final String[] projection, final String selection, final String[] selectionArgs,
			final String sortOrder) {

		final SelectionBuilder selectionBuilder = new SelectionBuilder();
		switch (getUriMatcher().match(uri)) {

			case ROUTE_DISCOVERY:
				selectionBuilder.where(STUNnerContract.Discovery._ID + "=?", uri.getLastPathSegment());
			case ROUTE_DISCOVERIES:
				selectionBuilder.setTable(STUNnerContract.Discovery.TABLE_NAME);
				break;
			case ROUTE_BATTERY_INFO:
				selectionBuilder.where(STUNnerContract.BatteryInfo._ID + "=?", uri.getLastPathSegment());
			case ROUTE_BATTERY_INFOS:
				selectionBuilder.setTable(STUNnerContract.BatteryInfo.TABLE_NAME);
				break;
			case ROUTE_WIFI_INFO:
				selectionBuilder.where(STUNnerContract.WifiInfo._ID + "=?", uri.getLastPathSegment());
			case ROUTE_WIFI_INFOS:
				selectionBuilder.setTable(STUNnerContract.WifiInfo.TABLE_NAME);
				break;
			case ROUTE_MOBILE_INFO:
				selectionBuilder.where(STUNnerContract.MobileNetInfo._ID + "=?", uri.getLastPathSegment());
			case ROUTE_MOBILE_INFOS:
				selectionBuilder.setTable(STUNnerContract.MobileNetInfo.TABLE_NAME);
				break;
			default:
				throw new UnsupportedOperationException(UNKNOWN_URI + uri);
		}

		selectionBuilder.where(selection, selectionArgs);
		final Cursor cursor = selectionBuilder.query(sqliteHelper.getReadableDatabase(), projection, sortOrder);
		final Context context = getContext();
		assert null != context;
		cursor.setNotificationUri(context.getContentResolver(), uri);

		return cursor;
	}

	@Override
	public int update(final Uri uri, final ContentValues values, final String selection, final String[] selectionArgs) {
		final SelectionBuilder builder = new SelectionBuilder();
		switch (getUriMatcher().match(uri)) {

			case ROUTE_DISCOVERY:
				builder.where(STUNnerContract.Discovery._ID + "=?", uri.getLastPathSegment());
			case ROUTE_DISCOVERIES:
				builder.setTable(STUNnerContract.Discovery.TABLE_NAME)
						.where(selection, selectionArgs);
				break;

			default:
				throw new UnsupportedOperationException(UNKNOWN_URI + uri);
		}

		final int count = builder.update(sqliteHelper.getWritableDatabase(), values);
		Log.i(this.getClass().getSimpleName(), "update: successfully updated " + count + " records.");
		final Context context = getContext();
		assert context != null;
		context.getContentResolver().notifyChange(uri, null, false);
		return count;
	}

	/**
	 * Apply the given set of {@link ContentProviderOperation}, executing inside a SQLite database transaction. All changes will be rolled
	 * back if any single one fails.
	 * 
	 * @param operations these operations will be executed
	 * @throws OperationApplicationException if any operation fail
	 * @return the collection of {@link ContentProviderResult}
	 */
	@Override
	public ContentProviderResult[] applyBatch(final ArrayList<ContentProviderOperation> operations) throws OperationApplicationException {
		final SQLiteDatabase database = sqliteHelper.getWritableDatabase();
		database.beginTransaction();

		try {

			final int numOfOperations = operations.size();
			final ContentProviderResult[] results = new ContentProviderResult[numOfOperations];
			for (int i = 0; i < numOfOperations; i++) {
				results[i] = operations.get(i).apply(this, results, i);
			}
			database.setTransactionSuccessful();
			return results;

		} finally {
			database.endTransaction();
		}
	}

}
